#!/usr/local/bin/python2.7
# encoding: utf-8
u'''
Exameca -- 

    Classe EProfil
    
@author:     David Trujillo, Pierre Puiseux
@copyright:  2013 All rights reserved.    
@license:    propriétaire
'''

import sys, os, math
from PyQt4 import QtGui
from PyQt4 import QtCore
#from PyQt4.uic import *
# from config import SOURCES_DIR
from utilitaires import Path
from utilitaires.utilitaires import dist
# from PyQt4.QtCore import (
#                           Qt, QAbstractTableModel, 
#                           QVariant, QModelIndex, 
#                           QAbstractItemModel,
#                           QPointF, QLineF
#                           )
# from PyQt4.Qt import SIGNAL
# from config import  VALIDATION_DIR
from utilitaires.utilitaires import (
                                     whoami, trace, 
                                     absCurv, splineCubique, 
                                     p2t, p2s, my2dPlot, 
                                     baryCentre, hardScale, rotate, symetrieAxe
                                     )
import numpy as np
# from scipy import interpolate
from graphics.common import pointsFromPolygon, polygonFromPoints
from utilitaires.utilitaires import segmentPlusProche

    
class EProfil(QtCore.QObject):
    """
    Un EProfil est moralement un np.ndarray de shape (N, 2). Il est donné par la property 'points' : points = self.points
    En réalité, se cache derrière :
        C'EST LUI QUI FAIT REFERENCE
        C'EST LUI QUI FAIT REFERENCE
    - un QPolygonF (self.qpolygon)
        C'EST LUI QUI FAIT REFERENCE
        C'EST LUI QUI FAIT REFERENCE
    
    Attributs:
    ---------
    self.qpolygon : un QPolygonF, de dimension N,  bord de fuite NON dédoublé
    self.name : le nom du profil
    Properties:
    ----------
    self.points : un np.ndarray(N,2) qui extrait de self.qpolygon les N points
        ATTENTION, à chaque appel à self.points l'intégralité du QPolygonF 
        est extrait et retourné.
        TODO: a optimiser
    Méthodes :
    --------
    - save(filename) sauvegarde texte simple, un point (=deux coordonnées) par ligne.
    """
    def __init__(self, **dump):
        """
        dump doit contenir une clé 'points' dont la valeur est un np.ndarray, ou un QPolygon ou une liste de points, ou un filename
        """
        if dump.has_key('parent') :
            parent = dump.pop('parent')
            super(EProfil, self).__init__(parent)
        else : 
            super(EProfil, self).__init__()
        try : 
            self.name = dump.pop('name')
        except KeyError :
            self.name = 'NoName'
        try : 
            self.qpolygon = dump.pop('points')
        except KeyError : 
            self.qpolygon = None
        self.basenodeindex = 0
        self.update()
        
    def _setQPolygon(self, points):
        self._qpolygon = polygonFromPoints(points)
    def _getQPolygon(self):
        return self._qpolygon
    qpolygon = property(_getQPolygon, _setQPolygon)
        
    def isClosed(self):
        '''self[0] == self[-1]'''
        if len(self) > 1 and dist(self.points[0], self.points[-1])<=1.0e-8:# np.all(self.points[0] == points[-1]):
            return True
        else : 
            return False
        
    def __len__(self):
        return self.qpolygon.size()
    
    def __call__(self, k):
        '''points du polygone lissé : Inutilisé'''
        return self.smoothpol[k]
    
    def __getitem__(self, key):
        """
        Pour accéder aux points du polygone (en lecture)
        Voir utilisation dans __setitem__() ou '__main__'
        """
        if isinstance(key, tuple) :#deux entiers
            k, col = key[0], key[1]
        elif isinstance(key, int) : #un entier'
            k, col = key, -1
        if col == -1 :
            return self.qpolygon[k].x(), self.qpolygon[k].y()
        if col == 0 : 
            return self.qpolygon[k].x()
        if col == 1 : 
            return self.qpolygon[k].y()
    
    def __setitem__(self, key, value):
        """
        mise à jour du polygone et du polygone seulement
        Typiquement, on pourra ecrire
        >>> p = EProfil([(1,1),(2,2),(3,3)])
        >>> p[1] = 20,30
        >>> print p[1]
        (20.0, 30.0)
        >>> print p[1,1]
        30.0
        """
        poly = self.qpolygon
        if isinstance(value, QtCore.QPointF) : 
            poly[key] = value
        else : 
            poly[key] = QtCore.QPointF(value[0],value[1])
        self.update()#indispensable pour mettre à jour self.points
    
    def _getInfo(self):
        infos = [
                u"<%s>"%type(self).__name__+u" ; name=%s"%self.name, 
                u'point de base=%s'%self.basenodeindex,
                u"nb points=%d"%len(self)+u" ; (dx,dy)=(%.1f,%.1f)"%(self.width, self.height),
#                u"tension spline=%.1f"%self.tension,
#                u"charges spline (min, max)=(%.1f,%.1f)"%(np.min(self.weights), np.max(self.weights)),
                ]
#        try : infos.append(u"nbp spline=%d"%self.nbplissage)
#        except AttributeError : pass
        return infos
    
    info = property(_getInfo)
    
    def __str__(self):
        return '\n'.join(self.infos)
    
    def appendPoint(self, pos, poids=1.0):
        i = self.points.shape[0]
        if not isinstance(pos, QtCore.QPointF) : 
            pos = QtCore.QPointF(*pos)
        self.qpolygon.append(pos)
#         self.weights = np.insert(self.weights, i, poids)
        self.update()
        return i
#        
    def insertPoint(self, pos, poids=1.0):
        i = segmentPlusProche(self.points, pos)
        if not isinstance(pos, QtCore.QPointF) : 
            pos = QtCore.QPointF(*pos)
        self.qpolygon.insert(i+1, pos)
#         self.weights = np.insert(self.weights, i+1, poids)
        self.update()
        return i

    def removePoint(self, pnum):
        poly = self.qpolygon
        point = poly[pnum]
        poly.remove(pnum)
        self.weights = np.delete(self.weights, pnum)
        self.update()
        return point
    
        
    def save(self, filename, lisse=True):
        toto = u'lissé' if lisse else 'brut' 
        if lisse : 
            try : 
                np.savetxt(filename, self.smoothpol)
                print>>sys.stdout, whoami(self), u"Sauvegarde polyline (%s) sur %s"%(toto,filename)
            except AttributeError as msg :
                print>>sys.stderr, msg, "Il semble qu'il n'y ait pas de polyline lissé. Veuillez lisser le polyline."
                return
        else : 
            super(EProfil, self).save(filename)
     
    def scaled(self, echelle):
        '''
        Retourne une COPIE de self, mise à l'échelle echelle=(echx, echy).
        Pas de modif de self
        '''
        points = self.points.copy()
        hardScale(self.points, echelle)
        name = '(%.2g,%.2g)x'%echelle
        try : name = name + self.name
        except TypeError : name = name + 'NoName'
        return EProfil(points=points, parent=self.parent(), name=name)
        
    def hardTranslate(self, vecteur):
        u = np.asarray(vecteur).reshape(1,2)
        trace(self, 'vecteur translation', u)
        points = self.points+u
        self.qpolygon = polygonFromPoints(points)
        self.update()
    
    def symetriser(self, axe):
        '''
        Modif du polyline lui meme. self est modifié.
        Mise à l'échelle facteur, centrée sur centre. (en fait, une homothétie)
        C'est à dire 
        >>> self[i] = centre + facteur*(self[i]-centre) 
        si centre == None on prend l'isobarycentre
        '''
        if len(self) == 0 : return
        centre = baryCentre(self)
        points = symetrieAxe(self.points, axe, centre[0][axe])
        self.qpolygon = polygonFromPoints(points)
        self.update()
        return
    
        
    def hardScale(self, facteur=(1.0,1.0), centre=None):
        '''
        Modif du polyline lui meme. self est modifié.
        Mise à l'échelle facteur, centrée sur centre. (en fait, une homothétie)
        C'est à dire 
        >>> self[i] = centre + facteur*(self[i]-centre) 
        si centre == None on prend l'isobarycentre
        '''
        
#         trace(self, 'len(self), facteur, centre', len(self), facteur, centre)
        if len(self) == 0 : return
        if centre is None : 
            centre = baryCentre(self)
        points = hardScale(self.points, facteur, centre)
        self.qpolygon = polygonFromPoints(points)
        self.update()
        return
    
    def hardRotate(self, alfa, centre):
        '''Rotation in-situ, self est modifié'''
#         trace(self, self.points.shape)
#         trace(self, self.points)
        points = rotate(self.points, alfa, centre)
#         trace(self, '###########',points)
        self.qpolygon = polygonFromPoints(points)
        self.update()
        
    def _getPoints(self):
        """
        Retourne un np.ndarray de shape (len(self), 2)
        TODO: optimiser, tant que les points n'ont pas changé, inutile de rappeler pointsFromPolygon
        """
        self._points = pointsFromPolygon(self.qpolygon, close=False)#a optimiser !!
        try : 
            return self._points
        except AttributeError : 
            return self._points
    points = property(_getPoints, None, None, "un np.ndarray FERMé (points[0] = points[-1])")

    
    def boundingRect(self):
        return self.qpolygon.boundingRect()

    def _getWidth(self):
        return self.qpolygon.boundingRect().width()
    width = property(_getWidth, None, None, "width's docstring")
    
    def _getHeight(self):
        return self.qpolygon.boundingRect().height()
    height = property(_getHeight, None, None, "height's docstring")
    
    def _getAbscisseCurviligne(self, i0=None, i1=None):
        try :  return self._abscurv
        except AttributeError :
            self._abscurv = absCurv(self.points)
            return self._abscurv 
    abscurv = property(_getAbscisseCurviligne)
    
    def _getCenter(self):
        '''isoBayrcentre'''
        if self.isClosed() : 
            return baryCentre(self.points[:-1]).flatten()
        else :
            return baryCentre(self.points).flatten()
    center = property(_getCenter)
    
    def update(self):
        '''Doit être appelé à chaque modification (géométrique) d'un point du profil'''
        try : del self._points
        except AttributeError : pass
        try : del self._abscurv
        except AttributeError : pass
        try : del self.smoothpol
        except AttributeError : pass
        

def testBasicEProfil():
    filename = Path(VALIDATION_DIR, 'unenervure2d.gnu')
    filename = Path(VALIDATION_DIR, 'simple','simple2d2.gnu')
    p = EProfil(points=None, parent=None)
    p = EProfil(points=filename)
    p.appendPoint((5,10))
    print " => Constructeur filename :",p
    points = p.points
    p = EProfil(points=points)
    print " => constructeur np.ndarray  :",p
    p = EProfil(points=p.qpolygon)
    print " => constructeur QPolygon  :",p
    print p.scaled((2,2))
    #print "constructeur QPolygon :",p
    #print '########## EProfil divers (BA, absCurv, extrados, ligne moyenne...) ##########'
    p.removePoint(0)
    curv = absCurv(p)
    dcurv = curv[1:]-curv[:-1]
    print dcurv
    print 'p.absCurv()',absCurv(p)
    print '########## EProfil geometrie ##########'
    p.hardScale((2,2))
    print p.points
    
    filename = Path(VALIDATION_DIR, 'simple','simple2d2.gnu')
    p = EProfil(points=filename)
    mp = EProfil(points=filename)
    centre = (7,0)
    mp.hardRotate(np.pi/6, centre)
    mp.hardScale((0.5,0.5), centre)
#     p = EProfil(points=([0,0],[1,0],[1,1]), parent=None)
    for k in range(1) :
        k
        XY0 = p.points
        XY = mp.points
        trace('',type(XY))
        my2dPlot((XY0, XY0, XY, XY, np.asarray(centre).reshape((1,2))), equal=True, cosmetic=('b-', 'bo', 'r-', 'ro', 'g^', 'r*', 'r^'))
        
if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    testBasicEProfil()
#     testLissageEProfil()
    sys.exit(app.exec_())
    